//
// Created by LiuYou on 2021/5/7.
//

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cassert>
#include <sys/poll.h>
#include <cerrno>
#include <unistd.h>
#include <string>
#include "ClientData.hpp"
#include "UserInfo.hpp"
#include <libgen.h>


using ClientEntry::ClientData;
using String = std::string;

int main(int argc, char** argv) {
    if (argc < 3) {
        std::fprintf(stderr, "用法: %s ip地址 端口号\n", basename(argv[0]));
        return -1;
    }
    /**************************************************************************/
    // 添加日志文件:
    std::string log(dirname(argv[0]));
    log += "/log";
    FILE* fp = std::fopen(log.data(), "a");
    time_t timeStamp{};
    tm* tp = nullptr;

    ClientEntry::occurrenceTime(fp, timeStamp, tp);
    std::fprintf(fp, "INFO - Server启动\n");
    std::fflush(fp);

    /**************************************************************************/

    const char* const ip = argv[1];
    int port = std::atoi(argv[2]);

    struct sockaddr_in serverSockAddrIn{
            .sin_family=PF_INET,
            .sin_port=htons(port),
            {.s_addr=0}
    };
    inet_pton(AF_INET, ip, &serverSockAddrIn.sin_addr);

    int listenFD = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenFD >= 0);

    int ret = bind(listenFD, (sockaddr*) &serverSockAddrIn, sizeof(serverSockAddrIn));
    assert(ret != -1);

    ret = listen(listenFD, 5);
    assert(ret != -1);

    const int FD_LIMIT = 65535;
    ClientData* clientsData = new ClientData[FD_LIMIT]();
    const int USER_LIMIT = 5;
    pollfd pollFDS[USER_LIMIT + 1];
    for (int i = 1; i < USER_LIMIT; ++i) {
        pollFDS[i].fd = -1;
        pollFDS[i].events = 0;
    }
    // poll轮询的第一个fd是listenFD, poll监听是否有新的client连接。
    pollFDS[0].fd = listenFD;
    // 监听listenFD 是否有新的client连接或者错误
    pollFDS[0].events = POLLIN | POLLERR;
    pollFDS[0].revents = 0;

    int clientsCounter{};
    for (;;) {
        ret = poll(pollFDS, clientsCounter + 1, -1);
        if (ret < 0) {
            std::fprintf(stderr, "poll()调用失败\n");
            ClientEntry::occurrenceTime(fp, timeStamp, tp);
            std::fprintf(fp, "ERROR - poll()调用失败\n");
            std::fflush(fp);
            break;
        }

        for (int i = 0; i < clientsCounter + 1; ++i) {

            if ((pollFDS[i].fd == listenFD) && (pollFDS[i].revents & POLLIN)) {
                // 如果poll监听的文件是listenFD, 而且listenFD由内核填充的事假发生的事件 有数据可读。(即有新的客户端连接)
                struct sockaddr_in clientSockAddrIn{};
                socklen_t clientSockAddrInLength = sizeof(clientSockAddrIn);
                // 使用新式的C++强转
                int connectionFD = accept(listenFD, reinterpret_cast<sockaddr*>(&clientSockAddrIn),
                                          &clientSockAddrInLength);
                if (connectionFD < 0) {
                    std::fprintf(stderr, "accept(): %s\n", std::strerror(errno));
                    ClientEntry::occurrenceTime(fp, timeStamp, tp);
                    std::fprintf(fp, "ERROR - accept(): %s\n", std::strerror(errno));
                    std::fflush(fp);
                    continue;
                }

                // TODO  注意: condition
                if (clientsCounter > USER_LIMIT) {
                    const char* const info = "\n太多用户连接\n";
                    std::fprintf(stderr, "%s", info);
                    ClientEntry::occurrenceTime(fp, timeStamp, tp);
                    std::fprintf(fp, "WARN - %s", info);
                    std::fflush(fp);
                    // TODO  注意: send给谁
                    send(connectionFD, info, std::strlen(info), 0);
                    close(connectionFD);
                    continue;
                }

                ++clientsCounter;
                // TODO 注意: 以空间来换取效率。
                clientsData[connectionFD].setSockAddrIn(clientSockAddrIn);

                //  先接受一下, 这个连接的Client的发送的用户名
                const int USER_NAME_SIZE = 1024;
                char userName[USER_NAME_SIZE]{};
                recv(connectionFD, userName, USER_NAME_SIZE, 0);
                clientsData[connectionFD].setUserName(userName);

                /**************************************************************************/
                // 实现用户鉴定
                // 如果有这个用户那就进行验证登录, 如果没有这个用户那就注册
//                ClientEntry::Users users;
//                users.readUsersInfoFromFile();
//                if (users.queryUsersByName(userName)) { ;
//                } else {
//                    close(pollFDS[connectionFD].fd);
//                    --clientsCounter;
//                    continue;
//                }
                /**************************************************************************/


                // 设置为非阻塞, 没有内容(读不到socket内容)我就走了。执行后续的代码。
                ClientEntry::setNonBlocking(connectionFD);
                // 如果是第一次执行到这里, 就是把第一个(连接到Server的Client的Server的)socket文件放到poll监听队列中,
                // 第一次的话就是pollFDS[i].fd = connectionFD; 而pollFDS[0]在监听listenFD。
                // TODO 注意: 上面这行的注释的 i 是 FD_LIMIT的值为 65535 的随便一个, 因为是以空间换取效率。
                //  ulimit -a 查看 为: open files                      (-n) 100001。
                //  这个就是以空间换取效率
                pollFDS[clientsCounter].fd = connectionFD;
                // 注册想要监听的事件: 可读、tcp连接被server关闭、错误
                pollFDS[clientsCounter].events = POLLIN | POLLRDHUP | POLLERR;
                pollFDS[clientsCounter].revents = 0;
                std::fprintf(stdout, "\n连入一个新的用户: %s, 现在有 %d 用户, 分别是:\n",
                             clientsData[connectionFD].getUserName().data(),
                             clientsCounter);
                ClientEntry::occurrenceTime(fp, timeStamp, tp);
                std::fprintf(fp, "INFO - 连入一个新的用户: %s, 现在有 %d 用户, 分别是:\n",
                             clientsData[connectionFD].getUserName().data(),
                             clientsCounter);
                std::fflush(stdout);
                for (int j = 1; j <= clientsCounter; ++j) {
                    std::fprintf(stdout, "%s:%s\n",
                                 clientsData[pollFDS[j].fd].getIp(),
                                 clientsData[pollFDS[j].fd].getUserName().data());
                    ClientEntry::occurrenceTime(fp, timeStamp, tp);
                    std::fprintf(fp, "%s:%s\n",
                                 clientsData[pollFDS[j].fd].getIp(),
                                 clientsData[pollFDS[j].fd].getUserName().data());
                    std::fflush(fp);
                }
                std::fflush(stdout);

                //  向每一个人通知某个用户上线信息
                std::string chatRoomUsers("当前聊天室成员为: ");
                for (int j = 1; j <= clientsCounter; ++j) {
//                    chatRoomUsers += clientsData[pollFDS[j].fd].getUserName();
                    chatRoomUsers.append(clientsData[pollFDS[j].fd].getUserName());
                    chatRoomUsers.append(", ");
                }
                chatRoomUsers.append("共计" + std::to_string(clientsCounter) + "人\n");
                for (int j = 1; j <= clientsCounter; ++j) {

                    pollFDS[j].events |= ~POLLIN;
                    // 期待数据可写这件事儿
                    pollFDS[j].events |= POLLOUT;
                    clientsData[pollFDS[j].fd].writeBuffer = clientsData[pollFDS[j].fd].buffer;
                    std::strcpy(clientsData[pollFDS[j].fd].writeBuffer, chatRoomUsers.data());
                }

            } else if (pollFDS[i].revents & POLLERR) {
                // TODO 没写这个逻辑
                //  这个逻辑是如果connectedFD产生了不可避免的错误怎么办
                std::fprintf(stdout, "发现一个错误: %d\n", pollFDS[i].fd);
                ClientEntry::occurrenceTime(fp, timeStamp, tp);
                std::fprintf(fp, "ERROR - 发现一个错误: %d\n", pollFDS[i].fd);
                std::fflush(fp);
                const int ERROR_SIZE = 100;
                char error[ERROR_SIZE]{};
                socklen_t size = ERROR_SIZE;
                if (getsockopt(pollFDS[i].fd, SOL_SOCKET, SO_ERROR, &error, &size) < 0) {
                    std::fprintf(stdout, "获取套接字选项失败\n");
                    ClientEntry::occurrenceTime(fp, timeStamp, tp);
                    std::fprintf(fp, "ERROR - 获取套接字选项失败\n");
                    std::fflush(fp);
                }
                continue;

            } else if (pollFDS[i].revents & POLLRDHUP) {
                std::string leaveUserName = clientsData[pollFDS[i].fd].getUserName();
                // 如果客户端关闭连接, 则服务器也关闭对应的连接, 并将连入到Server的Client总数减一。
                close(pollFDS[i].fd);
                // 在pollFDS数组中clientsCounter位置(即pollFDS记录的最后一个用户)的pollFDS的connectionFD来放到刚刚删除的这里。
                // 在纸上画一画帮助理解
                pollFDS[i] = pollFDS[clientsCounter];
                // 并且将ClientsData[clientsCounter]的数据也放到刚刚离开Server连接的那个客户端数据索引上。
                // TODO 注意: pollFDS[]与clientsData[]是一个映射的关系。clientsData[]采用了以空间换取效率。
                clientsData[pollFDS[i].fd] = clientsData[pollFDS[clientsCounter].fd];
                // 现在此时将刚刚离开的连接的pollFDS监听数组中移除了, 而且ClientData数组中保存的它数据也移除了。
                // TODO 注意: 这里是没有内存泄漏。

                --i;
                --clientsCounter;
                std::fprintf(stdout, "\n%s 离开了\n", leaveUserName.data());
                ClientEntry::occurrenceTime(fp, timeStamp, tp);
                std::fprintf(fp, "INFO - %s 离开了\n", leaveUserName.data());
                std::fflush(fp);

                //  向每一个人通知某个用户下线信息
                std::string chatRoomUsers("当前聊天室成员为: ");
                for (int j = 1; j <= clientsCounter; ++j) {
//                    chatRoomUsers += clientsData[pollFDS[j].fd].getUserName();
                    chatRoomUsers.append(clientsData[pollFDS[j].fd].getUserName());
                    chatRoomUsers.append(", ");
                }
                chatRoomUsers.append(leaveUserName + "离开了");
                chatRoomUsers.append("共计" + std::to_string(clientsCounter) + "人\n");
                for (int j = 1; j <= clientsCounter; ++j) {

                    pollFDS[j].events |= ~POLLIN;
                    // 期待数据可写这件事儿
                    pollFDS[j].events |= POLLOUT;
                    clientsData[pollFDS[j].fd].writeBuffer = clientsData[pollFDS[j].fd].buffer;
                    std::strcpy(clientsData[pollFDS[j].fd].writeBuffer, chatRoomUsers.data());
                }


            } else if (pollFDS[i].revents & POLLIN) {
                // 如果poll监听的某个fd有数据了, 那就读
                std::memset(clientsData[pollFDS[i].fd].buffer, '\0', ClientEntry::getBufferSize());
                // server接受数据, 写入buffer。
                ssize_t retVal = recv(pollFDS[i].fd, clientsData[pollFDS[i].fd].buffer,
                                      ClientEntry::getBufferSize() - 1,
                                      0);
                inet_ntop(AF_INET, &clientsData[pollFDS[i].fd].getSockAddrIn().sin_addr,
                          clientsData[pollFDS[i].fd].ip, INET_ADDRSTRLEN);
                std::fprintf(stdout,
                             "\n得到 %zd 字节的用户数据, 数据: (%s), 来自 %s:%s\n",
                             retVal,
                             clientsData[pollFDS[i].fd].buffer,
                             clientsData[pollFDS[i].fd].ip,
                             clientsData[pollFDS[i].fd].getUserName().data());
                ClientEntry::occurrenceTime(fp, timeStamp, tp);
                std::fprintf(fp,
                             "INFO - 得到 %zd 字节的用户数据, 数据: (%s), 来自 %s:%s\n",
                             retVal,
                             clientsData[pollFDS[i].fd].buffer,
                             clientsData[pollFDS[i].fd].ip,
                             clientsData[pollFDS[i].fd].getUserName().data());
                std::fflush(fp);
                if (retVal < 0) {
                    // 如果读操作出错, 则关闭连接
                    // TODO 这里和上面的client连接断开是一样的操作
                    //  不过这个errno现在不懂。
                    if (errno != EAGAIN) {
                        close(pollFDS[i].fd);
                        clientsData[pollFDS[i].fd] = clientsData[pollFDS[clientsCounter].fd];
                        pollFDS[i] = pollFDS[clientsCounter];
                        --i;
                        --clientsCounter;
                    }
                } else if (retVal == 0) {
                    // 没有对这个逻辑的描述
                    // 其实是可以删掉的。
                } else {

                    // 如何Server读到了某个Client的数据, 那么通过Server中连接其它Client的connectionFD准备写数据
                    // 这里的condition中的=号是一定要有的。因为要通知所有连接Server的客户端。
                    for (int j = 1; j <= clientsCounter; ++j) {
                        // 除了当前的connectionFD无需通知外
                        if (pollFDS[j].fd == pollFDS[i].fd) {
                            // TODO 注意: 这里是要添加的！！！！, 因为之后的发送数据的逻辑, 需要判断写的缓冲区是否为空。
//                            clientsData[pollFDS[j].fd].setWriteBuffer(nullptr);
//                            std::strcpy(clientsData[pollFDS[j].fd].writeBuffer, nullptr);
                            clientsData[pollFDS[j].fd].writeBuffer = nullptr;
                            continue;
                        }

                        pollFDS[j].events |= ~POLLIN;
                        // 期待数据可写这件事儿
                        pollFDS[j].events |= POLLOUT;
                        // 将当前的connectionFD的内容全部发送给其它连接到Server的Client。

//                        clientsData[pollFDS[j].fd].setWriteBuffer(clientsData[pollFDS[i].fd].getBuffer());
                        // TODO 注意: 这里是对上一句的改进, 因为想要实现私聊功能。
                        String msg(clientsData[pollFDS[i].fd].getUserName() + ":" +
                                   clientsData[pollFDS[i].fd].getBuffer());
                        clientsData[pollFDS[j].fd].writeBuffer = clientsData[pollFDS[j].fd].buffer;
//                        clientsData[pollFDS[j].fd].setWriteBuffer(const_cast<char*>(msg.data()));
                        std::strcpy(clientsData[pollFDS[j].fd].writeBuffer, msg.data());
                    }
                }

            } else if (pollFDS[i].revents & POLLOUT) {
                // TODO 这个逻辑分支不知道在干什么
                // 通过测试我知道了这个逻辑分支在干什么: 这个逻辑分支将上一个逻辑分支的客户端发来的内容一个个发送到其它的客户端上。
                int connectFD = pollFDS[i].fd;
                if (!clientsData[connectFD].writeBuffer) {
//                if (*clientsData[connectFD].writeBuffer == '\0') {
                    continue;
                }

                ssize_t retVal = send(connectFD, clientsData[connectFD].buffer,
                                      std::strlen(clientsData[connectFD].buffer), 0);

                // TODO 注意: 找到(发送数据的客户端的服务器的)connectedFD的fd, 因为之后要 来自客户端的需求
                //  这样的话要先在ClientData添加一个字段, 这个字段与Server的connectedFD进行配对
                int dataFrom{-1};
                for (int j = 1; j <= clientsCounter; ++j) {
                    if (clientsData[pollFDS[j].fd].writeBuffer) {
//                    if (*clientsData[pollFDS[j].fd].writeBuffer == '\0') {
                        dataFrom = pollFDS[j].fd;
                    }
                }

                std::fprintf(stdout, "\n成功向其它所有用户发送: %zd 字节的数据, 数据: (%s), 来自 %s:%s\n",
                             retVal,
                             clientsData[connectFD].buffer,
                             clientsData[dataFrom].getIp(),
                             clientsData[dataFrom].getUserName().data());
                ClientEntry::occurrenceTime(fp, timeStamp, tp);
                std::fprintf(fp, "INFO - 成功向其它所有用户发送: %zd 字节的数据, 数据: (%s), 来自 %s:%s\n",
                             retVal,
                             clientsData[connectFD].buffer,
                             clientsData[dataFrom].getIp(),
                             clientsData[dataFrom].getUserName().data());
                std::fflush(stdout);

                clientsData[connectFD].writeBuffer = nullptr;
                // 写完数据后需要重新注册pollFDS[i]上的可读事件
                // 监听是否有 非写 或者 可读
                pollFDS[i].events |= ~POLLOUT;
                pollFDS[i].events |= POLLIN;
            }
        }
    }

    delete[] clientsData;
    close(listenFD);

    return 0;
}


